import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import CannonDebugger from 'cannon-es-debugger';
import * as CANNON from 'cannon-es';
import * as THREE from "three";

import { alignMesh, convertToTree, getSize } from './tooler';
import { Bus, G2V_COORDINATE, G2V_SELECTED, G2V_SIZE, G2V_TREE, V2G_ADD_BODY, V2G_ADD_MESH, V2G_INIT_WORLD, V2G_REMOVE_MESH, V2G_RESET_MESH, V2G_SELECTED } from './Bus';
import { createBody, createGeometry } from './Factory';
// import { group } from 'console';
import { Car } from './Car';
import { sleep } from '@/core/tooler';
import { Road } from './Road';

const raycaster = new THREE.Raycaster();
let camera: THREE.PerspectiveCamera, scene: THREE.Scene, renderer: THREE.WebGLRenderer;
let stage: THREE.Object3D = new THREE.Object3D;
let boxHelper: THREE.BoxHelper;
let curObj: any;
let control: TransformControls;
let composer: EffectComposer;
let car: Car;

let cannonDebugger: CannonDebugger;
let physicsWorld: CANNON.World;

export function start(canvas: HTMLCanvasElement) {
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.z = 20;

    scene = new THREE.Scene();
    scene.add(stage);
    // scene.fog = new THREE.Fog(0x949494, 10, 200);

    renderer = new THREE.WebGLRenderer({ antialias: false, canvas });
    // renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    // renderer.toneMapping = THREE.ACESFilmicToneMapping;
    // renderer.toneMappingExposure = 1;

    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap

    // control = new TransformControls( camera, renderer.domElement );    
    // scene.add( control );

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.minDistance = 1;
    controls.maxDistance = 1000;
    // controls.target.set(0, 0, 0);
    controls.update();

    const loader = new GLTFLoader().setPath('/asset/3D/lowpoly_car_pack/');
    // const loader = new GLTFLoader().setPath('/asset/3D/asset_of_low-poly_cars_part_2/');
    loader.load('scene.gltf', async function (gltf) {
        const model = gltf.scene;
        console.log("gltf", gltf)
        // await renderer.compileAsync(model, camera, scene);
        const obj = model.children[0];
        stage.add(obj);

        obj.traverse(item => {
            item.castShadow = true;
            // item.rotation.set(0, 0, 0);
            // item.position.set(0, 0, 0);
            // item.scale.set(1, 1, 1);
        })

        // let temp:THREE.Mesh = obj.getObjectByName("Body_CarTex_1_0")
        // console.log(temp, "temp----")
        const size = getSize(obj);
        controls.target.set(0, size.y * -0.5, 0);
        // temp.geometry.translate(0,0,0.8644);
        // temp.geometry.rotateY(Math.PI);

        const plane = new THREE.Mesh(new THREE.PlaneGeometry(size.x * 4, size.z * 4), new THREE.MeshStandardMaterial({ color: 0x004466, side: THREE.DoubleSide }));
        plane.position.y = size.y * -0.5;
        plane.rotation.x = Math.PI * -0.5;

        plane.castShadow = true; //default is false
        plane.receiveShadow = true;

        scene.add(plane);

        updateTree();

        // setupPhysics();
    });


    addListener();
    scene.add(new THREE.AxesHelper(8));
    addLights();

    effect();

    animate();
}


async function setupPhysics() {
    physicsWorld = new CANNON.World({
        gravity: new CANNON.Vec3(0, -9.0, 0),
    });
    physicsWorld.broadphase = new CANNON.SAPBroadphase(physicsWorld);
    physicsWorld.defaultContactMaterial.friction = 0.2;
    // cannonDebugger = new CannonDebugger(scene, physicsWorld);

    stage.traverse(item => {
        item.rotation.set(0, 0, 0);
        item.position.set(0, 0, 0);
        item.scale.set(1, 1, 1);
        setTimeout(() => {
            if (item.type == "Mesh") {
                console.log(++t, item)
                alignMesh(item);
            }
        }, 30);

    });

    await sleep(300)

    let carBody = stage.getObjectByName("Body_CarTex_1_0")!;
    let carWheel = stage.getObjectByName("FLWheel_WheelTex_0")!;
    console.log(carBody, "carBody")

    await alignMesh(carBody);
    // await alignMesh(carWheel);

    // setTimeout(() => {
    car = new Car(physicsWorld, carBody, carWheel);
    // }, 30);

    let road = new Road(1.2, 100, 100);
    road.init(physicsWorld)
    stage.add(road.mesh)

    setTimeout(() => {
        car && car.meshes.forEach(i => stage.add(i));
    }, 150);

}

function effect() {
    const renderScene = new RenderPass(scene, camera);

    const params = {
        threshold: 0,//0
        strength: 0,//1
        radius: 0.1,//0
        exposure: 1,//1
    };


    const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
    bloomPass.threshold = params.threshold;
    bloomPass.strength = params.strength;
    bloomPass.radius = params.radius;

    const outputPass = new OutputPass();

    composer = new EffectComposer(renderer);
    composer.addPass(renderScene);
    composer.addPass(bloomPass);
    composer.addPass(outputPass);
}

function addListener() {
    window.addEventListener('resize', onWindowResize);
    renderer.domElement.addEventListener("click", onCanvasClick);
    Bus.on(V2G_SELECTED, (id: any) => {
        choose(stage.getObjectById(id));
    });

    Bus.on(V2G_ADD_MESH, (val: any) => {
        const { obj, coordinate } = val;
        let mesh = new THREE.Mesh(createGeometry(obj)!, new THREE.MeshNormalMaterial());
        if (curObj) {
            curObj.add(mesh);
        }
        else {
            stage.add(mesh);
        }
        mesh.position.set(coordinate.x, coordinate.y, coordinate.z);
        updateTree();
    });

    Bus.on(V2G_ADD_BODY, (val: any) => {
        const { obj, coordinate } = val;
        // let mesh = new THREE.Mesh(createBody(obj)!, new THREE.MeshNormalMaterial());

        let body = createBody(obj);
        physicsWorld.addBody(body!);

    })

    Bus.on(V2G_REMOVE_MESH, (id: any) => {
        let mesh = stage.getObjectById(id);
        if (mesh) {
            mesh.parent?.remove(mesh);
            updateTree();
            choose(null);
        }
    })

    Bus.on(V2G_RESET_MESH, (id: any) => {
        let obj = stage.getObjectById(id);
        let t = 0;
        obj?.traverse(item => {
            item.rotation.set(0, 0, 0);
            item.position.set(0, 0, 0);
            item.scale.set(1, 1, 1);
            setTimeout(() => {
                if (item.type == "Mesh") {
                    console.log(++t, item)
                    alignMesh(item);
                }
            }, 30);

        });
        setTimeout(() => {
            choose(obj);
            // setupPhysics();
        }, 300);

    })

    Bus.on(V2G_INIT_WORLD, () => {
        setupPhysics();
    })
}

function updateTree() {
    const tree = convertToTree(stage);
    Bus.emit(G2V_TREE, tree.children);
}

function onCanvasClick(event: MouseEvent) {
    const mouse = new THREE.Vector2();
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(stage.children, true);

    let obj = null;
    if (intersects.length > 0) {
        const worldPosition = intersects[0].point;
        console.log("world", worldPosition);
        Bus.emit(G2V_COORDINATE, worldPosition);
        for (let i = 0; i < intersects.length; i++) {
            obj = intersects[0].object;
            break;
        }
    }
    choose(obj);
}

function addLights() {
    let g1 = new THREE.DirectionalLight(0xffffff, 0.32);
    g1.position.set(-10, 10, 20);
    scene.add(g1);
    g1.castShadow = true;

    // g1.shadow.mapSize.width = 512; // default
    // g1.shadow.mapSize.height = 512; // default
    // g1.shadow.camera.near = 0.5; // default
    // g1.shadow.camera.far = 500; // default

    let g2 = new THREE.DirectionalLight(0xff0000, 0.4);
    g2.position.set(4, 10, -20);
    scene.add(g2);
    g2.castShadow = true;

    let mesh1 = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshNormalMaterial());
    scene.add(mesh1);
    mesh1.position.set(-0.55, -0.1, -2);

    let mesh2 = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshNormalMaterial());
    scene.add(mesh2);
    mesh2.position.set(0.55, -0.1, -2);

    const spot1 = new THREE.SpotLight(0xffffff, 4);
    let p1 = mesh1.position.clone();
    p1.z += 0.1;
    spot1.position.copy(p1);
    spot1.target.position.set(-0.55, -0.1, -20);
    scene.add(spot1);

    const spot2 = new THREE.SpotLight(0xffffff, 4);
    let p2 = mesh2.position.clone();
    p2.z += 0.1;
    spot2.position.copy(p2);
    spot2.target.position.set(0.55, -0.1, -20);
    scene.add(spot2);

    change(spot1);
    change(spot2);

    function change(lgh: THREE.SpotLight) {
        lgh.penumbra = 1;
        lgh.decay = 2;
        lgh.distance = 20;

        lgh.castShadow = true;

        lgh.shadow.mapSize.width = 256;
        lgh.shadow.mapSize.height = 256;
        lgh.shadow.camera.near = 1.8;
        lgh.shadow.camera.far = 20;
        lgh.shadow.focus = 1;

        lgh.angle = Math.PI * 0.32;
        lgh.target.updateMatrixWorld();

        // let hp = new THREE.CameraHelper(lgh.shadow.camera);
        // scene.add(hp);
    }

    let box = new THREE.Mesh(new THREE.BoxGeometry(8, 6, 0.5), new THREE.MeshStandardMaterial({ color: 0x339955 }));
    box.castShadow = true;
    box.receiveShadow = true;
    box.position.set(0, 0, -4)
    scene.add(box);

    // const rectLight1 = new THREE.RectAreaLight(0xff0000, 1, 3, 4);
    // rectLight1.position.set(-0.6, 0.2, 3);
    // rectLight1.rotateY(180);
    // rectLight1.updateMatrix();
    // scene.add(rectLight1);

    // control.attach( rectLight1 );

    // const rectLight2 = new THREE.RectAreaLight(0xff0000, 1, 3, 4);
    // rectLight2.position.set(0.6, -0.2, 3);
    // scene.add(rectLight2);

    // addBox(new THREE.Vector3(3, 4, 0.2), rectLight1.position);
}

function choose(obj: any) {
    if (boxHelper) {
        scene.remove(boxHelper);
        boxHelper.dispose()
    }
    if (obj) {
        boxHelper = new THREE.BoxHelper(obj, 0xffff00);
        scene.add(boxHelper);
    }
    curObj = obj;

    let size = null;
    if (obj) {
        size = getSize(obj);
    }

    Bus.emit(G2V_SIZE, size);
    Bus.emit(G2V_SELECTED, obj);
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    composer.setSize(window.innerWidth, window.innerHeight);
    render();
}

function animate() {
    requestAnimationFrame(animate);
    render();
}

function render() {
    // renderer.render(scene, camera);
    if (physicsWorld) {
        physicsWorld.fixedStep();
        // cannonDebugger.update();
        car && car.meshes.forEach(obj => {
            let body = obj.userData.body;
            if (!body) return;
            obj.position.copy(body.position);
            obj.quaternion.copy(body.quaternion);
        });
    }

    composer.render();
}